using System;
using System.Collections.Generic;
using System.Text;

namespace AopLoadBalancer.Client
{
    [Serializable]
    class ClusterHosts
    {
        private class HostDataEntry
        {
            private DateTime invalidTo;
            private DateTime lastUsed;


            public DateTime InvalidTo
            {
                get { return invalidTo; }
                set { invalidTo = value; }
            }

            public DateTime LastUsed
            {
                get { return lastUsed; }
                set { lastUsed = value; }
            }

            public bool IsValid
            {
                get { return invalidTo < DateTime.Now; }
            }


            public HostDataEntry()
            {
                invalidTo = DateTime.MinValue;
                lastUsed = DateTime.MinValue;
            }
        }

        //TODO move to config
        /// <summary>
        /// After invalidating url for invalidationTimeout host url won't be used
        /// </summary>
        private readonly  TimeSpan invalidationTimeout=new TimeSpan(0,0,2,0);

        /// <summary>
        /// List of hosts and assigned date time after which url is useful.
        /// If url is usefeul always it may have assigned DateTime.Min value
        /// </summary>
        private Dictionary<string, HostDataEntry> hosts = new Dictionary<string, HostDataEntry>();


        public ClusterHosts(String initialUrl)
        {
            hosts.Add(initialUrl,new HostDataEntry());

            hosts[initialUrl].LastUsed = DateTime.Now;
        }

        public void UpdateHosts(IEnumerable<String> newHosts)
        {
            //host whicvh should be removed from list because they are no longer avaialble
            List<String> notAvailableHosts = new List<string>(hosts.Keys);

            foreach (string newHost in newHosts)
            {
                //mark as available
                notAvailableHosts.Remove(newHost);

                if(!hosts.ContainsKey(newHost))
                {
                    hosts.Add(newHost, new HostDataEntry());
                }
            }

            foreach (string notAvailableHost in notAvailableHosts)
            {
                hosts.Remove(notAvailableHost);
            }
        }

        public void Invalidate(string host)
        {
            //for invalidation Timeout won't be useful
            hosts[host].InvalidTo = DateTime.Now.Add(invalidationTimeout);
        }

        public string GetNextUrl()
        {
            if(hosts.Count==0)
            {
                throw new InvalidOperationException("No url has been registred yet.");
            }

            bool validOnly=true;
            string url = GetLeastRecentlyUsedUrl(validOnly);

            //only invalid url's left in list
            if(url==null)
            {
                validOnly = false;
                url = GetLeastRecentlyUsedUrl(validOnly);
            }

            return url;
        }

        private string GetLeastRecentlyUsedUrl(bool validOnly)
        {
            string lruUrl = null;
            DateTime lruTime = DateTime.MaxValue;

            foreach(string url in hosts.Keys)
            {
                if (hosts[url].IsValid == false && validOnly)
                {
                    continue;
                }

                if (hosts[url].LastUsed < lruTime)
                {
                    lruTime = hosts[url].LastUsed;
                    lruUrl = url;
                }
            }

            hosts[lruUrl].LastUsed = DateTime.Now;

            return lruUrl;
        }
    }
}
